#pragma once

using std::function;
using std::unique_ptr;
using std::make_unique;
using std::shared_ptr;
using std::make_shared;
using std::string;
using std::string_view;
using std::stringstream;
using std::any;
using std::optional;
using std::variant;
using std::tuple;
using std::array;
using std::vector;
using std::list;
using std::forward_list;
using std::numeric_limits;
using std::valarray;
using std::istream;
using std::ostream;
using std::fstream;
using std::ifstream;
using std::ofstream;

struct PyieldError : public std::runtime_error{
    using std::runtime_error::runtime_error;
};

[[nodiscard]]constexpr bool is_empty(const auto& a) noexcept{
    return std::size(a) == 0;
}
using std::size;
[[nodiscard]]constexpr const auto& front(const auto& a){
    return a.front();
}
[[nodiscard]]constexpr auto& front(auto& a){
    return a.front();
}
[[nodiscard]]constexpr const auto& back(const auto& a){
    return a.back();
}
[[nodiscard]]constexpr auto& back(auto& a){
    return a.back();
}
inline auto& clear(auto& xs){
    xs.clear();
    return xs;
}

inline auto& push_back(auto& xs, const auto& a){
    xs.push_back(a);
    return xs;
}
inline auto& push_front(auto& xs, const auto& a){
    xs.push_front(a);
    return xs;
}
inline auto& pop_back(auto& xs){
    xs.pop_back();
    return xs;
}
inline auto& pop_front(auto& xs){
    xs.pop_front();
    return xs;
}
template <typename T>
inline const T& stack_top(const vector<T>& xs){
    return xs.back();
}
template <typename T>
inline T& stack_top(vector<T>& xs){
    return xs.back();
}
template <typename T>
inline auto& stack_push(vector<T>& xs, const T& a){
    return push_back(xs, a);
}
template <typename T>
inline auto& stack_pop(vector<T>& xs){
    if (! xs.empty()){
        pop_back(xs);
    }
    return xs;
}

void _String_(ostream&){}
template <typename HEAD, typename...TAIL>
inline void _String_(ostream& str, HEAD head, TAIL...tail){
    _String_(str << head, tail...);
}
template <typename HEAD, typename...TAIL>
inline string String(HEAD head, TAIL...tail){
    stringstream ss;
    _String_(ss, head, tail...);
    return ss.str();
}
inline string uppercase(string_view str) noexcept{
    string r(str);
    for (auto& c : r){c = (char)(std::toupper(c));}
    return r;
}
inline string lowercase(string_view str) noexcept{
    string r(str);
    for (auto& c : r){c = (char)(std::tolower(c));}
    return r;
}
vector<string> split(string_view str, char symbol){
    vector<string> result;
    string part;
    for (char c : str){
        if (symbol == c){
            push_back(result, part);
            clear(part);
        }else{part += c;}
    }
    if (size(part) > 0){push_back(result, part);}
    return result;
}
vector<string> split_ignore_empty(string_view str, char symbol){
    vector<string> result;
    string part;
    for (char c : str){
        if (symbol == c){
            if (size(part) > 0){
                push_back(result, part);
                clear(part);
            }
        }else{part += c;}
    }
    if (size(part) > 0){push_back(result, part);}
    return result;
}

struct SingleLine{
    string_view _raw;
    char        _quote;
    SingleLine(string_view raw, char quote=0):_raw(raw),_quote(quote){}
};
inline SingleLine Quoted(string_view raw, char sym='"'){
    return SingleLine(raw, sym);
}
ostream& operator<<(ostream& str, const SingleLine& text){
    if (text._quote != 0){str << text._quote;}
    for (const auto c : text._raw){
        switch (c){
        case '\t': str << "\\t"; break;
        case '\n': str << "\\n"; break;
        case '\r': str << "\\r"; break;
        case '\\': str << "\\\\"; break;
        default: if (c < ' '){
                str << "\\0x" << std::setfill('0') << std::setw(2) << std::hex << c;
            }else if (c == text._quote){
                str << '\\' << text._quote;
            }else{
                str << c;
            }break;
        }
    }
    if (text._quote != 0){str << text._quote;}
    return str;
}
string getline(string_view prompt){
    auto mem = readline::readline(prompt.data());
    auto str = string(mem);
    delete mem;
    return str;
}

const fs::path PATH_DIR_CACHE("./cache/");
const fs::path PATH_DIR_DATA("./data/");
const fs::path PATH_DIR_DOC("./doc/");
const fs::path PATH_DIR_EXE("./exe/");
const fs::path PATH_DIR_LOG("./log/");

namespace logger{
    namespace _hide{
        ofstream _file; 
        void raii(){
            if (_file.is_open()){
                _file.close();
            }
        }
    }
    void set_file(const fs::path& filename, std::ios_base::openmode mode=std::ios::trunc){
        _hide::raii();
        const auto path = PATH_DIR_LOG / fs::path(filename).replace_extension(".log.txt");
        _hide::_file.open(path, std::ios::binary | mode);
    }
    void set_file_append(const fs::path& filename){
        set_file(filename, std::ios::app);
    }
    void log(string_view text, string_view prefix=""){
        if (_hide::_file.is_open()){
            const auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
            _hide::_file << prefix << '[' << std::put_time(std::localtime(&now), "%F %T") << ']' << text << std::endl;
        }
    }
#if defined(DEBUG)
    void debug(string_view text){
        log(text, "Debug");
    }
#else
    [[maybe_unused]]void debug(string_view){}
#endif
}

#define Main int _pyield_main(const vector<string_view>& args)
Main;

int main(int argc, char* argv[]) try{
    std::ios::sync_with_stdio(false);
    vector<string_view> args;
    if (argc > 1){
        args.reserve(argc-1);
        for (int i = 1; i < argc; i++){
            push_back(args, string_view(argv[i]));
        }
    }
    auto rax = _pyield_main(args);
    logger::_hide::raii();
    return rax;
}catch (PyieldError& e){
    std::cout << "Error: " << e.what() << std::endl;
    return 1;
}catch (std::exception& e){
    std::cout << "Expected-Termination: " << e.what() << std::endl;
    return 2;
}catch (...){
    std::cout << "Termination for Unknown Cause." << std::endl;
    return 3;
}

